package com.yang.security.token.impl;

import com.yang.security.model.LoginUser;
import com.yang.security.token.TokenService;
import com.yang.security.model.Token;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;


/**
 * Created by baipan
 * Date: 2019-12-18
 * token存到redis的实现类
 * jwt实现token
 */
@Primary
@Service
@Slf4j
public class TokenServiceJWTImpl implements TokenService {

	/**
	 * 一周
	 */
	private static final Integer expireSeconds = 3600 * 24 * 7;

	private static final String jwtSecret = "zhy2019";

	private static final String LOGIN_USER_KEY = "LOGIN_USER_KEY";

	private static Key KEY = null;

	@Autowired
	private RedisTemplate<String, Object> redisTemplate;


	/**
	 * 登录成功保存token
	 */
	@Override
	public Token saveToken(LoginUser loginUser) {
		loginUser.setToken(UUID.randomUUID().toString());
		cacheLoginUser(loginUser);
		String jwtToken = createJWTToken(loginUser);
		return new Token(jwtToken, loginUser.getLoginTime());
	}

	/**
	 * 将token信息缓存到Redis
	 */
	private void cacheLoginUser(LoginUser loginUser) {
		loginUser.setLoginTime(System.currentTimeMillis());
		//从登录时间起算，过期时间设置为 一周
		loginUser.setExpireTime(loginUser.getLoginTime() + expireSeconds * 1000);
		// 根据uuid将loginUser缓存
		String tokenKey = getTokenKey(loginUser.getToken());
		BoundValueOperations<String, Object> operations = redisTemplate.boundValueOps(tokenKey);
		operations.set(loginUser, expireSeconds, TimeUnit.SECONDS);
	}


	private String getTokenKey(String uuid) {
		return "tokens:" + uuid;
	}


	private String createJWTToken(LoginUser loginUser) {
		Map<String, Object> claims = new HashMap<>();
		// 放入一个随机字符串，通过该串可找到登陆用户
		claims.put(LOGIN_USER_KEY, loginUser.getToken());
		return Jwts.builder()
				.setClaims(claims)
				.signWith(SignatureAlgorithm.HS256, getKeyInstance())
				.compact();
	}


	private Key getKeyInstance() {
		if (KEY == null) {
			synchronized (TokenServiceJWTImpl.class) {
				if (KEY == null) {
					byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(jwtSecret);
					KEY = new SecretKeySpec(apiKeySecretBytes, SignatureAlgorithm.HS256.getJcaName());
				}
			}
		}
		return KEY;
	}


	@Override
	public void refresh(LoginUser loginUser) {
		cacheLoginUser(loginUser);
	}


	@Override
	public LoginUser getLoginUser(String token) {
		String uuid = getUUIDFromJWT(token);
		if (uuid != null) {
			String tokenKey = getTokenKey(uuid);
			return (LoginUser) redisTemplate.boundValueOps(tokenKey).get();
		}
		return null;
	}


	private String getUUIDFromJWT(String token) {
		if (StringUtils.isBlank(token)) {
			return null;
		}
		try {
			Map<String, Object> jwtClaims = Jwts.parser()
					.setSigningKey(getKeyInstance())
					.parseClaimsJws(token)
					.getBody();
			return MapUtils.getString(jwtClaims, LOGIN_USER_KEY);
		} catch (ExpiredJwtException e) {
			log.error("{}已过期", token);
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}


	/**
	 * 从缓存中删掉token
	 * 可以当做退出登录
	 */
	@Override
	public boolean deleteToken(String token) {
		String uuid = getUUIDFromJWT(token);
		if (uuid != null) {
			String key = getTokenKey(uuid);
			LoginUser loginUser = (LoginUser) redisTemplate.opsForValue().get(key);
			if (loginUser != null) {
				redisTemplate.delete(key);
			}
			return true;
		}
		return false;
	}




}
